using System;
using System.Collections.Generic;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
using OhioState.AssetManaged;
using OhioState.Graphics;


namespace OhioState.Controls.AssetMgr
{
    //
    // AssetManagerControl - This is a generic DataGridView control
    //  designed to view, select, preview, and rename the contents of the asset
    //  manager.  It can be initialized to view objects of a certain type or
    //  simply all of the objects in the asset manager.
    //
    public partial class AssetManagerControl : UserControl
    {
        //
        // Desired Type - "" = everything
        //
        List<Type> typeList;

        //
        // Preview Size Property
        //
        Size previewSize;
        int previewPadding;

        //
        // Property to allow control to try to automatically select the
        //  item it believes is correct.
        //
        bool autoSelect;

        //
        // Event for changing selection and notifying about items being updated
        //
        public event IAssetManagedListHandler SelectedEvent;
        public event IAssetManagedHandler UpdatedEvent;

        //
        // Asset Manager events
        //
        private IAssetManagedHandler assetManAddItemEvent;
        private IAssetManagedHandler assetManRemoveItemEvent;

        //
        // Event helper for firing events
        //
        protected EventsStrategyAbstract _eventsHelper;

        //
        // Constructor
        //
        public AssetManagerControl()
        {
            InitializeComponent();

            //_eventsHelper = new EventsStrategyOneWay();
            _eventsHelper = new EventsStrategySerial();

            //
            // set size defaults
            //
            PreviewColumnOn = true;
            previewPadding = 2;
            PreviewSize = new Size(44, 14);

            autoSelect = false;
            typeList = new List<Type>();
        }

        //
        // Init function sets the type that this control will be watching for.
        //  It also gets a pointer to the asset manager, which enables the control
        //  to work.  If this function has not been called, the control will not
        //  perform any tasks.
        //  This function also populates the data grid view off of what is currently in 
        //   the asset manager at creation time.
        //  If desired_type is the null then this control will create a
        //   view of all types of objects.
        //
        public void Init(List<Type> desired_type)
        {
            //
            // Set up event handlers for add and remove items
            //
            assetManAddItemEvent = new IAssetManagedHandler(assetManager_AddItemEvent);
            AssetManager.Instance.AssetAdded += assetManAddItemEvent;
            assetManRemoveItemEvent = new IAssetManagedHandler(assetManager_RemoveItemEvent);
            AssetManager.Instance.AssetRemoved += assetManRemoveItemEvent;

            // Set type
            typeList = desired_type;

            // Ensure the data grid is clear of anything that may have been in it before
            dataGridView.Rows.Clear();

            //
            // Populate data grid view
            //
            foreach (Type ty in typeList)
            {
                List<IAssetManaged> list = AssetManager.Instance.FindAll(ty);
                foreach (IAssetManaged asset in list)
                {
                    assetManager_AddItemEvent_thisThread(asset);
                }
            }
        }

        #region Public properties

        //
        // Public properties
        //  - Enable or disable Preview column in the data grid view
        //  - Change Row height of data grid view
        //  - Change the size of the preview image
        //  - Modify the header text: default is "Name"
        //  - Toggle the column headers on and off
        //  - Toggle ability to rename items
        //  - Toggle auto selection
        //
        public bool PreviewColumnOn
        {
            get { return dataGridView.Columns["PreviewColumn"].Visible; }
            set { dataGridView.Columns["PreviewColumn"].Visible = value; }
        }
        public int RowHeight
        {
            get { return dataGridView.RowTemplate.Height; }
            set { dataGridView.RowTemplate.Height = value; }
        }
        public int PreviewPadding
        {
            get { return previewPadding; }
            set
            {
                previewPadding = value;
                UpdatePreviewSize();
            }
        }
        public Size PreviewSize
        {
            get { return previewSize; }
            set
            {
                previewSize = value;
                UpdatePreviewSize();
            }
        }
        public String ColumnHeaderText
        {
            get { return dataGridView.Columns["NameColumn"].HeaderText; }
            set { dataGridView.Columns["NameColumn"].HeaderText = value; }
        }
        public bool ColumnHeadersVisible
        {
            get { return dataGridView.ColumnHeadersVisible; }
            set { dataGridView.ColumnHeadersVisible = value; }
        }
        public bool ReadOnly
        {
            get { return dataGridView.ReadOnly; }
            set { dataGridView.ReadOnly = value; }
        }
        public bool AutoSelect
        {
            get { return autoSelect; }
            set { autoSelect = value; }
        }
        public bool MultiSelect
        {
            get { return dataGridView.MultiSelect; }
            set { dataGridView.MultiSelect = value; }
        }
        //
        // helper function to help update preview size
        //
        private void UpdatePreviewSize()
        {
            RowHeight = previewSize.Height + 2 * previewPadding;
            dataGridView.Columns["PreviewColumn"].Width = previewSize.Width + 2 * previewPadding;
        }

        #endregion

        //
        // Select the row of the data grid view that this object is in.
        //  The result of this is that multiple rows can be selected as this
        //  only adds the list of currently selected rows.
        //
        public void SelectItem(IAssetManaged asset)
        {
            foreach (DataGridViewRow row in dataGridView.Rows)
            {
                IAssetManaged data = (IAssetManaged)row.Cells["DataColumn"].Value;
                if (data == asset)
                {
                    row.Selected = true;
                    break;
                }
            }
        }

        //
        // Select only the row of the data grid view that this object is in.
        //  The result of this is that only one row is selected at max.
        //
        public void SelectOneItem(IAssetManaged asset)
        {
            ClearSelection();
            SelectItem(asset);
        }

        //
        // Deselect every item
        //
        public void ClearSelection()
        {
            dataGridView.ClearSelection();
        }

        //
        // Returns the currently selected item or null if there is none.
        //
        public List<IAssetManaged> GetSelected()
        {
            List<IAssetManaged> list = new List<IAssetManaged>();
            foreach (DataGridViewRow row in dataGridView.SelectedRows)
            {
                IAssetManaged asset = (IAssetManaged)row.Cells["DataColumn"].Value;
                // Apparently this can sometimes be null... wtf?
                if(asset != null)
                    list.Add(asset);
            }
            return list;
        }

        //
        //Return number of entries/rows
        //

        public int Entries()
        {
            return dataGridView.RowCount;
        }

        //
        // Event handler that gets triggered when an object is added to the asset manager.
        //  This function first checks if it is the correct type of object before adding
        //  it to the data grid view.  If the object is Previewable, it will get a preview and
        //  set up the necessary events to update the preview after changes.
        //
        public void assetManager_AddItemEvent_thisThread(IAssetManaged asset)
        {
            Type assetType = asset.GetType();
            //
            // Determine if this is an item that we want.  Check the type as well as base types.
            //
            foreach (Type interestedType in typeList)
            {
                if (interestedType.IsAssignableFrom(assetType))
                {
                    AddAssetToGrid(asset);
                    break;
                }
            }
        }

        private void AddAssetToGrid(IAssetManaged asset)
        {
            int row = dataGridView.Rows.Add();
            dataGridView.Rows[row].Cells["NameColumn"].Value = asset.Name;
            dataGridView.Rows[row].Cells["DataColumn"].Value = asset;

            //
            // Get a preview of the object if it is IPreviewable.
            //
            if (typeof(IPreviewable).IsAssignableFrom(asset.GetType()))
            {
                Bitmap bitmap = null;
                GetPreview(asset, ref bitmap);
                if (bitmap != null)
                {
                    ((DataGridViewImageCell)dataGridView.Rows[row].Cells["PreviewColumn"]).Value = bitmap;
                }
            }

            //
            // Set up event handler call back to redraw the preview of the object
            //
            IAssetManagedHandler changed = new IAssetManagedHandler(iAssetManaged_UpdateChangedEvent);
            asset.AddChangedEventHandler(changed);
            dataGridView.Rows[row].Cells["EventHandlerColumn"].Value = changed;

            if (autoSelect)
            {
                // select the proper row
                ClearSelection();
                dataGridView.Rows[row].Selected = true;
            }

            _eventsHelper.Fire(UpdatedEvent, asset);
        }

        private void GetPreview(IAssetManaged asset, ref Bitmap bitmap)
        {
            if (bitmap == null)
                bitmap = new Bitmap(previewSize.Width, previewSize.Height);
            //
            // This part is tricky so pay attention.  We want a copy of this object on the
            //  local computer instead of on the remote computer when remoting.  If the object
            //  is stored on the remote computer, when we ask for the preview it creates the 
            //  preview image on the remote computer and sends the image over the internet.
            //  This is very slow and completely unneccesary because we should be able to create
            //  the preview locally at a much greater speed than sending an uncompressed bitmap
            //  over the internet.  To solve this problem we must create a local copy and call
            //  the preview routine from that object.  To create a local copy a copy constructor
            //  is used.  This seems pretty straight forward until we realize that we don't what
            //  type this object is going to be until runtime.  This creates problems because we
            //  don't know what the name of the constructor is.  Here we use the GetType() function
            //  and the GetConstructor() function to find out the type of the object and retrieve the
            //  proper constructor.  We then invoke this constructor which creates a local copy.
            //  We can then get a preview from this object.
            //
            try
            {
                //((IPreviewable)asset).Preview(bitmap);

                //
                // Get the constructor of the object and invoke it
                //
                Type objType = asset.GetType();
                Type[] types = new Type[1];
                types[0] = objType;
                // Get the constructor that takes an integer as a parameter.
                ConstructorInfo constructorInfoObj = objType.GetConstructor(types);
                if (constructorInfoObj != null)
                {
                    object[] args = new object[1];
                    args[0] = asset;

                    IPreviewable temp = (IPreviewable)constructorInfoObj.Invoke(args);
                    if (temp != null)
                    {
                        temp.Preview(bitmap);
                    }
                }
            }
            catch { }
        }

        //
        // Force the event to get called on the proper thread.  The DataGridView complains if you try
        //  to use it on the wrong thread.
        //
        public void assetManager_AddItemEvent(IAssetManaged asset)
        {
            IAssetManagedHandler newEvent = new IAssetManagedHandler(assetManager_AddItemEvent_thisThread);
            object[] args = new object[1];
            args[0] = asset;
            //
            // We need to make sure these methods are executed on the correct thread.
            //
            try
            {
                if (this.InvokeRequired)
                    this.Invoke(newEvent, args);
                else
                    newEvent(asset);
            }
            catch { }
        }

        //
        // Removes item from data grid view when the remove event is received from the
        //  asset manager.  Update events are also removed.
        //
        public void assetManager_RemoveItemEvent_thisThread(IAssetManaged asset)
        {
            foreach (DataGridViewRow row in dataGridView.Rows)
            {
                if (row.Cells["DataColumn"].Value == asset)
                {
                    //
                    // Remove event handler call back to redraw the preview of the object
                    //
                    IAssetManagedHandler changed = (IAssetManagedHandler)row.Cells["EventHandlerColumn"].Value;
                    if(changed != null)
                        asset.RemoveChangedEventHandler(changed);

                    dataGridView.Rows.Remove(row);

                    _eventsHelper.Fire(UpdatedEvent, asset);
                    break;
                }
            }
        }
        //
        // Force the event to get called on the proper thread.  The DataGridView complains if you try
        //  to use it on the wrong thread.
        //
        public void assetManager_RemoveItemEvent(IAssetManaged asset)
        {
            IAssetManagedHandler newEvent = new IAssetManagedHandler(assetManager_RemoveItemEvent_thisThread);
            object[] args = new object[1];
            args[0] = asset;
            //
            // We need to make sure these methods are executed on the correct thread.
            //
            this.Invoke(newEvent, args);
        }

        //
        // Update event that gets called when an object is changed.  This is gets a new
        // preview of the asset and displays it in the data grid view.  It also updates the name.
        //
        public void iAssetManaged_UpdateChangedEvent_thisThread(IAssetManaged asset)
        {
            foreach (DataGridViewRow row in dataGridView.Rows)
            {
                if (row.Cells["DataColumn"].Value == asset)
                {
                    //
                    // Update the name
                    //
                    row.Cells["NameColumn"].Value = asset.Name;

                    //
                    // Get a preview of the object is it is IPreviewable.
                    //
                    if (asset.GetType().GetInterface("IPreviewable") != null)
                    {
                        Bitmap bitmap = (Bitmap)((DataGridViewImageCell)row.Cells["PreviewColumn"]).Value;
                        if (bitmap != null)
                        {
                            GetPreview(asset, ref bitmap);
                        }

                        // This step should be unnecessary in theory, its the same object
                        //((DataGridViewImageCell)row.Cells["PreviewColumn"]).Value = bitmap;
                        dataGridView.Invalidate();
                    }
                    _eventsHelper.Fire(UpdatedEvent, asset);
                    break;
                }
            }
        }
        //
        // Force the event to get called on the proper thread.  The DataGridView complains if you try
        //  to use it on the wrong thread.
        //
        public void iAssetManaged_UpdateChangedEvent(IAssetManaged asset)
        {
            IAssetManagedHandler newEvent = new IAssetManagedHandler(iAssetManaged_UpdateChangedEvent_thisThread);
            object[] args = new object[1];
            args[0] = asset;
            //
            // We need to make sure these methods are executed on the correct thread.
            //
            try
            {
                if (this.InvokeRequired)
                    this.Invoke(newEvent, args);
                else
                    newEvent(asset);
            }
            catch { }
        }

        //
        // Selection changed event which gets fired when the selection of the data grid view changes.
        //  This in turn fires another event updating controls that might want to know that the selection
        //  has changed.
        //
        private void dataGridView_SelectionChanged(object sender, EventArgs e)
        {
            _eventsHelper.Fire(SelectedEvent, GetSelected());
        }

        //
        // Edit the name of cell
        //  This renames the object whose text field was just changed in the data grid view.
        //
        private void dataGridView_CellEndEdit(object sender, DataGridViewCellEventArgs e)
        {
            if (dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex] == dataGridView.Rows[e.RowIndex].Cells["NameColumn"])
            {
                IAssetManaged asset = (IAssetManaged)dataGridView.Rows[e.RowIndex].Cells["DataColumn"].Value;
                //asset.BeginManipulation();
                asset.SetName((String)dataGridView.Rows[e.RowIndex].Cells["NameColumn"].Value);
                //asset.EndManipulation();
            }
        }

        // 
        // Key up
        //  - Check for delete key hit to delete selected items
        //
        private void dataGridView_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Delete)
            {
                List<IAssetManaged> list = GetSelected();
                foreach (IAssetManaged asset in list)
                {
                    AssetManager.Instance.Remove(asset);
                }
                e.Handled = true;
            }
        }

        // 
        // Key down
        //  Check for F2 key hit to begin edit
        //
        private void dataGridView_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.F2)
            {
                dataGridView.BeginEdit(true);
                e.Handled = true;
            }
            if ((e.KeyCode == Keys.Return) || (e.KeyCode == Keys.Enter))
            {
                dataGridView.BeginEdit(true);
                e.Handled = true;
            }
        }

        //
        // Cell Double Click
        //  Begin editing on double click in a cell
        //
        private void dataGridView_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            dataGridView.BeginEdit(true);
        }
    }
}